HELP = """
功能：查找sw ui log或maco log所有confition code和其他信息
运行方法：python 脚本路径 参数
可选参数：
-h help，打印帮助信息
-f file，指定log文件（夹）路径， 如 -f C:\\KS00620\\Desktop\\MTC-xxx， 如不指定，则认为第一个参数是路径， 如： python conditon_code.py xxx.log
-t time，只显示指定时间段内的code，如： -t 2024-01-10_18:30:00~2024-01-15，必须严格按照此格式输入
-r remove，移除不想显示的code，如：-r PVD-012;PA0-038，不显示PVD-012和PA0-038
-s show，显示指定的code， 如：-s PVD-012，只显示PVD-012
-p path，显示code所在文件路径，写 -p即可
-c count，显示当前code是第几次出现的，写 -c即可
-m merge，合并重复的codeID（配合-c，则在后面显示该codeID出现的总次数），写 -m即可
-o order，排序, 默认按时间，可以设置：-o code，来按codeID排序
--save 指定路径保存，如果只指定名字，则保存到log所在目录，如：--save codeID_result, 则保存到log所在目录，命名为codeID_result.log
-sim 只显示code和info
"""
import os, sys, re
import time, datetime
import traceback
import copy
import xlwt


class XLS:
    instance = None
    def __new__(cls, **kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls,**kwargs)
            cls.workbook = xlwt.Workbook()
        return cls.instance
    
    def __init__(self, **kwargs):
        self.style = xlwt.XFStyle()
        self.style.alignment.wrap = 1 
        self.style.alignment.horz = 0
        self.style.alignment.vert = 1
        self.title_style = copy.deepcopy(self.style)

        ft = xlwt.Font()
        # ft.name = "Times New Roman"
        # self.style.font = copy.deepcopy(ft)
        ft.bold = True 
        ft.colour_index = 1
        pattern = xlwt.Pattern()
        pattern.pattern = xlwt.Pattern.SOLID_PATTERN
        pattern.pattern_fore_colour = xlwt.Style.colour_map["blue_gray"]
        self.title_style.pattern = pattern
        self.title_style.font = ft
        self.title_style

        if "width" in kwargs:
            width = kwargs.pop("width")
        else:
            width = 5000
        self.width = width
    
    def write_table(self, sheetname, datas, title=None, width=None):
        sheet = self.workbook.add_sheet(sheetname)
        if datas:
            for i in range(len(datas[0])):
                sheet.col(i).width = self.width
            if width:
                for i in width:
                    sheet.col(i[0]).width = i[1]
            # 写标题
            if title:
                for i, text in enumerate(title):
                    sheet.write(0, i, text, self.title_style)
            # 写数据
            for i, cells in enumerate(datas):
                for j, text in enumerate(cells):
                    sheet.write(i+1, j, text, self.style)
        return sheet
    
    def save(self, path):
        self.workbook.save(path)


def parse_argv(arg):
    '''
    获取运行脚本命令行给定参数（--xxx）对应的值
    如果参数没有给定值，则返回True
    如果找不到参数，返回False
    '''
    if arg in sys.argv:
        next_i = sys.argv.index(arg)+1
        if next_i < len(sys.argv):
            next_str = sys.argv[next_i]
            if not next_str.startswith("-"):
                return next_str
        return True 
    return False


def argv_files(path=None, index=1):
    """
        遍历sys.argv指定的路径的所有文件
        路径默认为-f参数对应的值，如果-f不存在，则默认为sys.argv的第一个值
    """
    if len(sys.argv)>index and path is None:
        path = next((i for i in [parse_argv('-f'), parse_argv('--file')] if i), sys.argv[index])
    if not os.path.exists(path):
        print(f"路径{path}不存在")
        return
        
    if os.path.isfile(path):
        yield path
    else:
        for cur, dirs, files in os.walk(path):
            for file in files:
                yield os.path.join(cur, file)


def parse_time(t):
    # 转换用户指定时间为datetime类型
    # 2024-01-10_08:00:00
    day = t.split("_")[0]
    h, m, s = '23', '59', '59'
    if "_" in t:
        clock = t.split("_")[1].split(":")
        h = clock[0]
        if len(clock)>1:
            m = clock[1]
        if len(clock)>2:
            s = clock[2]
    t = datetime.datetime.strptime(f"{day}_{h}:{m}:{s}", '%Y-%m-%d_%H:%M:%S')
    return t


def get_sysargv_time():
    # 获取用户指定时间
    t = parse_argv("-t")
    start, end = None, None
    if t:
        # 2024-01-16_08:00:00~2024-01-10_16:00:00
        try:
            start = t.split('~')[0]
            start = parse_time(start)
            if "~" in t:
                end = t.split("~")[1]
                end = parse_time(end)
                if end<start:
                    start, end = end, start
            print("搜索时间段：", start, "~",  end)
        except:
            traceback.print_exc()
            print("时间参数有误， 请按此格式输入：2024-01-16_08:00:00~2024-01-16_10:00:00")
    return start, end


class BaseLog:
    # 20240726 添加
    start, end = get_sysargv_time()
    time_p = r'\d{4}-\d{0,2}-\d{0,2} \d{2}:\d{2}:\d{2}.\d{3}'

    # 统一定义Maco和GUI log的condition code过滤器
    @classmethod
    def show_hide_codes(cls, ret):
        '''
            只显示 或者 不显示 某些code
        '''
        # 不显示指定code
        hidden_list = parse_argv("-r")
        if hidden_list:
            return [c for c in ret if c[1] not in hidden_list.split(";")]
        # 只显示指定code
        show_list = parse_argv("-s")
        if show_list:
            return [c for c in ret if c[1] in show_list.split(";")]
        return ret
    
    @classmethod
    def merge_count_codes(cls, ret):
        '''
            合并code
        '''
        # 统计code出现的次数
        if '-c' in sys.argv:
            code_d = {}
            ret_count = []
            for code in ret:
                if code[1] not in code_d:
                    code_d[code[1]] = 1
                    # count = "".ljust(4)
                else:
                    code_d[code[1]] += 1
                count = f"({code_d[code[1]]})".ljust(4)
                code.insert(2, count)
                ret_count.append(code) # 在第二个为此插入出现的次数
            ret = ret_count

        # 合并重复的code，只显示最后一次出现的
        if '-m' in sys.argv:
            ret_merge = []
            code_list = []
            for i, code in enumerate(ret):
                if code[1] not in code_list:
                    code_list.append(code[1])
                    ret_merge.append(code)
                else: # 如果前面已经添加过，则用现在最新的替换
                    ret_merge[code_list.index(code[1])] = code
            ret = ret_merge
        return ret
    
    @classmethod
    def order_codes(cls, ret):
        '''
            排序code
        '''
        # 按时间排序
        for r in ret:
            r.append(datetime.datetime.strptime(r[0].split(".")[0], '%Y-%m-%d %H:%M:%S'))
        ret.sort(key=lambda x:x[-1]) 
        ret = [i[:-1] for i in ret]
        order_type = parse_argv('-o')
        if order_type=='code':
            ret.sort(key=lambda x:x[1])
        if order_type=='count':
            ret.sort(key=lambda x:x[2])
        return ret 
    
    @classmethod
    def filter_codes(cls, codes):
        '''
            过滤数据：时间段限制、 按时间排序、 移除code、 指定code、 是否显示code对应的文件路径、显示第几次出现的、 合并重复项、 
            显示code类型和code详细信息
        '''
        codes = cls.show_hide_codes(codes)
        codes = cls.merge_count_codes(codes)
        codes = cls.order_codes(codes)
        if '-sim' in sys.argv: # 简化显示
            BaseLog.code_title = ["code", "description"]
            codes = [[i[1],i[4]] for i in codes]
        return codes

    def __init__(self, path, content=None):
        self.path = path
        if content:
            self.content = content
        else:
            with open(path, 'rb') as f:
                content = f.read()
                try:
                    self.content = content.decode("gbk")
                except:
                    try:
                        self.content = content.decode("utf-8")
                    except:
                        self.content = str(content)
                        print(f"读取文件{path}内容失败")
    
    def filter_time(self):
        times = re.finditer(self.time_p, self.content)
        start, end = 0,0
        for t in times:
            if self.start:
                if self.parse_time2(t.group()) > self.start and not start:
                    # print("过滤开始时间", t.group() )
                    start = t.start()
            if self.end:
                if self.parse_time2(t.group()) > self.end and not end:
                    # print("过滤结束时间", t.group())
                    end = t.start()+1
        if start and end:
            self.content = self.content[start:end]
        elif start:
            self.content = self.content[start:]
        elif self.start:
            self.content = ''

    def update_result(self):
        self.filter_time() # 默认过滤时间
        for i,p in enumerate(self.patterns):
            ret = re.findall(p['pattern'], self.content) 
            if p['name'] not in self.data:
                self.data[p['name']] = []
            self.data[p['name']] += ret
            # 过滤数据
    
    def filter_result(self):
        '''
            建议获取完全部数据后再调用
        '''
        for i,p in enumerate(self.patterns):
            if 'filter' in self.patterns[i]: # 需要写过滤器
                if p['name'] in self.data:
                    self.data[p['name']] = self.patterns[i]['filter'](self.data[p['name']])

    @property
    def log_type(self):
        if re.search(r'\n\['+self.time_p, self.content):
            return "maco"
        elif re.search(r'\n'+self.time_p, self.content):
            return 'GUI'
        else:
            return "NA"
    
    def parse_time2(self, time_str):
        # 解析log里的时间
        return datetime.datetime.strptime(time_str.split(".")[0], '%Y-%m-%d %H:%M:%S')
    

class MacoLog(BaseLog):
    def codeID_filter(ret):
        ret = [[i[0], i[3], f"[{i[1]}]", f"[{i[2]}]", ''] for i in ret]
        ret = BaseLog.filter_codes(ret)
        return ret
    
    def temps_filter(ret):
        ret = [[i[0]]+ i[1].split(",") for i in ret]
        return ret

    def incub_filter(ret):
        # for Yuting
        ret_new = ['*' for i in range(23)]
        for t in ret:
            incub = t[1].split('  ')[:-1]
            if len(incub)==24:
                for i, inc in enumerate(incub):
                    if inc!='*':
                        ret_new[i] = inc
        print("Incubator占用：\n", ret_new)
        if "*" not in ret_new:
            print("23 个位置都被占满了")
        else:
            print("有位置为*")

    patterns = [
            {   "name":"temps",  # 获取SLSU、孵育盘等环境监控温度数据
                # 数据保存到表格的标题
                "title": ["time", "env_temp", "slsu inner", "slsu outer", "slsu inner humidity", "slsu outer humidity", "slin temp1", "slin temp2"],
                # 搜索： [['2024-07-16 14:41:46.061', '25.63', ' 23.02', ' 22.91', ' 13.40', ' 34.33', ' 31.69', ' 31.58'], ...]
                # 对应 [时间, env_temp, slsu inner, slsu outer, slsu inner humidity, slsu outer humidity, slin temp1, slin temp2]
                "pattern": r"\[(\d{4}\-\d{2}\-\d{2}.*?)\].+?GetEnvSensor\(\).+?\[(.+?)\]",
                "filter": temps_filter # 数据过滤器
            },
            # for yu ting
            {   "name": "Incub", # 获取孵育盘23个槽是否全部被占用过
                "pattern": r"\[(\d{4}\-\d{2}\-\d{2}.*?)\].+?Last Cycle Assay.+?\n.+?\n.+?\[info\] (.+?)\n",
                'filter': incub_filter
            },
            {
                "name": "Maco_CodeIDs", # 获取condition codes
                "title": ["time", "code", "count", "type", "description"],
                # 搜索： 时间，code，信息，类型，次数，路径
                "pattern": r"\[(.*?)\] \[([a-zA-Z]+?)\] \[([a-zA-Z]+?)\] post condition code to UI  (.*?)\n",
                "filter": codeID_filter
            }
        ] # 正则过滤结果
    data = {}

    def __init__(self, path, content=None):
        super().__init__(path, content)

    @property
    def is_me(self):
        if re.search(r'\n\['+self.time_p, self.content):
            return True
        return False
        

class GUILog(BaseLog):
    def codeID_filter(ret):
        ret = [[i[0], f"{i[3]}-{i[4]}", '[]', f"[{i[1]}]", i[2]] for i in ret]
        ret = BaseLog.filter_codes(ret)
        return ret
    
    patterns = [
        {   "name":"GUI_CodeIDs",
             # 输出格式： 时间，错误级别，code信息，code1, code2
            "title": ["time", "code", "count", "type", "description"],
            "pattern": r"\n([\d\-:\. ]+?) ([a-zA-Z]+?) .+?CONDITION_EVENT\{.+?wShortText='(.+?)'.+?zErrorModule='(.+?)'.+?zErrorNumber='(.+?)'.+?\}",
            "filter": codeID_filter
        }
    ]
    data = {}

    def __init__(self, path, content=None):
        super().__init__(path, content)

    @property
    def is_me(self):
        if re.search(r'\n'+self.time_p, self.content):
            return True
        return False


def print_codes(name, codes, arrow=False):
    string = ""
    string += f"\n{name}codes:\n"
    for i,c in enumerate(codes):
        string+=f"{i+1}. ".rjust(4)
        for n in c:
            string += f"{n}  "
        string += "\n"
        if arrow:
            string += "----> \n"
    string+=f"总共：{len(codes)}个\n\n"
    print(string)
    return string               


def process_log(paths):
    # 获取数据
    for path in argv_files(paths):
        if '-a' in sys.argv or path.endswith(".log"):
            maco = MacoLog(path)
            if maco.is_me:
                maco.update_result()
            else:
                gui = GUILog(None, content=maco.content) # 使用content，避免再打开一次文件
                gui.update_result()
    # 获取完全部数据后再一次性过滤
    string = ''
    if MacoLog.data:
        maco.filter_result()
        string += print_codes("中位机", maco.data["Maco_CodeIDs"])

    if GUILog.data:
        gui.filter_result()
        string += print_codes("上位机", gui.data["GUI_CodeIDs"], True)

    # 保存数据
    save_dir = os.path.dirname(paths) if os.path.isfile(paths) else paths
    save_path = parse_argv("--save")
    if save_path:
        if not os.path.exists(save_path):
            save_path = os.path.join(save_dir, save_path)
            if not save_path.endswith(".txt"):
                save_path += '.txt'
        # 保存condition code到文本
        with open(save_path, 'w', encoding='utf-8') as f:
            f.write(string)
            print("结果保存在", save_path)

        # 20240725添加，保存结果到表格
        xls_path = os.path.join(save_dir, 'log_output.xls')
        xls = XLS()
        for c in [MacoLog, GUILog]:
            for p in c.patterns:
                if p['name'] in c.data and 'title' in p:
                    xls.write_table(p['name'],  c.data[p['name']], p['title'], width=[(0, 2000)])
        xls.save(xls_path)


def main():
    # if len(sys.argv)>1:
    #     # print(sys.argv)
    #     if '-h' in sys.argv:
    #         return print(HELP)
    #     # 获取log路径，如果不指定-f，则取第一个参数
    #     if('-f') in sys.argv:
    #         path = parse_argv('-f')
    #     else:
    #         path = sys.argv[1]
    #     if not os.path.exists(path):
    #         print("给定路径不存在！")
    #         return
    #     process_log(path)
    # else:
    #     print(HELP)

    if '-h' in sys.argv or '--h' in sys.argv or '-help' in sys.argv:
        return print(HELP)
    path = input("输入文件或目录路径-_-： ").strip('"')
    print(path, os.path.exists(path))
    process_log(path)



if __name__=="__main__":
    main()
    